﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl;
using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Statements;
using Weazel.Badger.Vhdl.Expressions;

using Weazel.Badger.Vhdl.Operators;
using Weazel.Badger.Vhdl.Operators.Binary;

#endregion

namespace Weazel.Badger
{
	/// <summary>
	/// This class represents a finite statemachine controller/datapath
	/// entity in VHDL.
	/// </summary>
  public class FiniteStateMachineEntity : ConversionEntity
  {
    /// <summary>
    /// Enumeration type containing each of the possible
    /// states of the FSM
    /// </summary>
    private LitteralBasedEnumerationType stateType;

    /// <summary>
    /// signal used to store the 'current state' of
    /// the FSM
    /// </summary>
    private Signal stateSignal;

    /// <summary>
    /// Enumeration type containing each of the possible 
    /// 'commands' the datapath may perform. 
    /// </summary>
    private LitteralBasedEnumerationType commandType;

    /// <summary>
    /// signal to hold the 'current' command of the 
    /// datapath
    /// </summary>
    private Signal commandSignal;

    private Vhdl.Process combinatorialProcess;
    private Vhdl.Process registerUpdateProcess;
    private Vhdl.Process regProcess;
    private Vhdl.Process cmbProcess;

    private ExpressionConverter regProcessesExprConverter;
    private ExpressionConverter cmbProcessesExprConverter;

    private List<FsmState> fsmStates = new List<FsmState>();
		
    /// <summary>
    /// Internal class used to represent an FSM state. Here a state is a name
    /// and a list of SFG's active.
    /// </summary>
    private class FsmState
    {
      public string Name;
      public List<string> ActiveSfgNames = new List<string>();

      public FsmState(string[] activeSfgs)
      {
        StringBuilder str = new StringBuilder();
        foreach (string s in activeSfgs)
        {
          ActiveSfgNames.Add(s);

          str.Append(s);
          str.Append('_');
        }

        str.Remove(str.Length - 1, 1);

        Name = str.ToString();
      }
    }

    public FiniteStateMachineEntity(ConversionContext conversionContext, Gezel.Model.Controller controller) 
			: base(conversionContext, controller)
    {
      this.regProcessesExprConverter = new ExpressionConverter(this, null);
      this.cmbProcessesExprConverter = new ExpressionConverter(this, null);
    }

		/// <summary>
		/// Attach child datapaths. This can only be done once all datapaths has been converted
		/// </summary>
		public void ConvertChildDatapaths()
		{
			foreach(string childDatapathName in controller.Datapath.ChildDatapathRefs.Keys)
			{
				// ComponentDeclaration component = new ComponentDeclaration(childDatapathName);??
			}
		}

    public override void Convert()
    {		
      createStateType();
      createCommandType();

      registerUpdateProcess = RegisterUpdateProcess.Get(this);
      AddProcess(registerUpdateProcess);

      buildCombinatorialProcess();
 
      regProcess = new Weazel.Badger.Vhdl.Process("reg", this);
      AddProcess(regProcess);

      cmbProcess = new Weazel.Badger.Vhdl.Process("cmb", this);
      AddProcess(cmbProcess);

      buildRegAndCmdProcesses();

			if(ConversionContext.Configuration.ConvertDisplayDirectives)
				setupDisplayProcess();
    }

    private void setupDisplayProcess()
    {
      CaseStatement caseStmt = new CaseStatement();
      caseStmt.Expression = 
				new SignalReferenceExpression(
					DisplayProcess.Statements,
					this.commandSignal
				);

      foreach (FsmState state in fsmStates)
      {
        CaseStatementChoice choice = new CaseStatementChoice(
          new EnumerationMemberReferenceExpression(
						DisplayProcess.Statements,
						commandType, 
						state.Name
					)
        );

        foreach (string sfg in state.ActiveSfgNames)
        {
          StatementCollection statements = ConvertedSignalFlowGraphExpressions[sfg].DisplayStatements;

          if (statements.Count > 0)
          {
            choice.Statements.Add(new Comment("display directive from sfg: " + sfg));
            choice.Statements.Add(statements);           
          }
          choice.Statements.Add(new EmptyLine());
        }

        caseStmt.AddChoice(choice);
      }

      this.DisplayProcess.AddStatement(caseStmt);
    }

    private void buildRegAndCmdProcesses()
    {
      regProcess.AddSignalToSensivitityList(ClockPort);
      regProcess.AddSignalToSensivitityList(ResetPort);

      StatementCollection regResetStatements = new StatementCollection();
      SignalAssignmentStatement resetStateStatement = 
        new SignalAssignmentStatement(
          this.stateSignal, 
          new Vhdl.Expressions.EnumerationMemberReferenceExpression(
						regResetStatements,
            stateType, 
            "state_" + controller.InitialState.LocalName
          )
        );

      regResetStatements.Add(resetStateStatement);

      StatementCollection regClockStatements = new StatementCollection();

      CaseStatement regProcessCaseStatement = new CaseStatement();
      regProcessCaseStatement.Expression = 
				new SignalReferenceExpression(
					regClockStatements,
					stateSignal
				);

      CaseStatement cmbProcessCaseStatement = new CaseStatement();
      cmbProcessCaseStatement.Expression = 
				new SignalReferenceExpression(
					cmbProcess.Statements,
					stateSignal
				);

      foreach (Weazel.Gezel.Model.State state in controller.States.Values)
      {
        if (!controller.Transitions.ContainsKey(state.LocalName))
        {
          // most likely the state is not used
          string message = string.Format("state '{0}' has no outgoing transitions.", state.LocalName);
          ConversionSetup.ConversionResult.Warnings.Add(new ConversionWarning(message));
          continue;
        }

        CaseStatementChoice regChoice = 
					new CaseStatementChoice(
						new Vhdl.Expressions.EnumerationMemberReferenceExpression(
							regClockStatements,
							stateType, 
							"state_" + state.LocalName
						)
					);

        CaseStatementChoice cmbChoice = 
					new CaseStatementChoice(
						new Vhdl.Expressions.EnumerationMemberReferenceExpression(
						cmbProcess.Statements,
						stateType, 
						"state_" + state.LocalName
					)
				);

        createStateMachine(
          controller.Transitions[state.LocalName], 
          regChoice.Statements, 
          cmbChoice.Statements
        );

        regProcessCaseStatement.AddChoice(regChoice);
        cmbProcessCaseStatement.AddChoice(cmbChoice);
      }

      regClockStatements.Add(regProcessCaseStatement);
      cmbProcess.Statements.Add(cmbProcessCaseStatement);

      regProcess.Statements.Add(
        ProcessSensitivityStatement.Get(
          this, 
					regProcess,
          regResetStatements, 
          regClockStatements
        )
      );

      foreach (Signal signal in cmbProcessesExprConverter.SignalsCreated)
        AddDeclarativeItem(signal);

      foreach (Signal signal in cmbProcessesExprConverter.SignalsRead)
      {
        if (signal is Port && ((Port)signal).PortDirection == PortDirection.Out)
        {
          throw new Exception();
        }
        else
        {
          cmbProcess.AddSignalToSensivitityList(signal);
        }
      }

      foreach (ConstantDeclaration constant in cmbProcessesExprConverter.Constants)
        cmbProcess.AddDeclarativeItem(constant);

      cmbProcess.AddSignalToSensivitityList(stateSignal);
      cmbProcess.AddSignalToSensivitityList(commandSignal);


    }

    /// <summary>
    /// Builds the combinatorial process. 
    /// </summary>
    private void buildCombinatorialProcess()
    {
      combinatorialProcess = new Weazel.Badger.Vhdl.Process("combinatorial", this);
      AddProcess(combinatorialProcess);

      // list of signals to initialize (set 0) ..
      List<Vhdl.Signal> initializationList = new List<Weazel.Badger.Vhdl.Signal>();

      // list of signals read (should be included in the sensitivity list)
      List<Vhdl.Signal> signalsRead = new List<Weazel.Badger.Vhdl.Signal>();

      // create a case-statement and choices for each of the states in the FSM      
      CaseStatement caseStatement = new CaseStatement();
      caseStatement.Expression = 
        new Vhdl.Expressions.SignalReferenceExpression(
					combinatorialProcess.Statements,
					commandSignal
				);
      
      foreach (FsmState state in fsmStates)
      {
        CaseStatementChoice choice = 
          new CaseStatementChoice(
            new Vhdl.Expressions.EnumerationMemberReferenceExpression(
							combinatorialProcess.Statements,
              commandType, 
              state.Name
            )
          );

        foreach (string activeSfg in state.ActiveSfgNames)
        {
          choice.Statements.Add(new Comment("-------------------------------------------"));
          choice.Statements.Add(new Comment(string.Format(" from sfg: {0}", activeSfg)));
          choice.Statements.Add(new Comment("-------------------------------------------"));

          ExpressionConverter ec = ConvertedSignalFlowGraphExpressions[activeSfg];         
          ec.Statements.CopyTo(choice.Statements);
          choice.Statements.Add(new EmptyLine());

          // add all signals created during conversion of the
          // expressions of this sfg
          foreach (Vhdl.Signal signal in ec.SignalsCreated)
          {
            AddSignal(signal);
            AddDeclarativeItem(signal);
          }
        
          // add all signals written to the list of signals
          // to be initialized
          foreach (Vhdl.Signal signal in ec.SignalsWritten)
            if (!initializationList.Contains(signal))
              initializationList.Add(signal);

          foreach (Vhdl.Signal signal in ec.SignalsRead)
            if (!signalsRead.Contains(signal))
              signalsRead.Add(signal);

          foreach (Vhdl.ConstantDeclaration constant in ec.Constants)
            if(!combinatorialProcess.ConstainsConstant(constant))
              combinatorialProcess.AddDeclarativeItem(constant);


        }

        caseStatement.AddChoice(choice);
      }

      combinatorialProcess.Statements.InsertFirst(new Comment("end of initialization"));

      // Add signal and register initialization in front of the process
      foreach (Vhdl.Signal signal in initializationList)
      {
         if (signal is RegisterReadSignal)
        {
          // read signals does not need initialization
          continue;
        }
        else if (signal is RegisterWriteSignal)
        {
          RegisterWriteSignal register = signal as RegisterWriteSignal;

          // registers are initialized using their present value
          SignalAssignmentStatement stmt = new SignalAssignmentStatement(
            register,
            new SignalReferenceExpression(
							combinatorialProcess.Statements,
							register
						)
          );

          combinatorialProcess.Statements.InsertFirst(stmt);
        }
        else
        {
          // signals are initialized to either '0' for 1 bit types or
          // others=>'0' for other (vector) types
          SequentialStatement stmt = ClearSignalStatement.Get(signal, combinatorialProcess.Statements);
          combinatorialProcess.Statements.InsertFirst(stmt);
        }
      }

      combinatorialProcess.Statements.InsertFirst(new EmptyLine());

      combinatorialProcess.Statements.Add(caseStatement);

      // make this process sensitive to all signals read
      foreach (Vhdl.Signal signal in signalsRead)
      {
        if (signal is Port && ((Port)signal).PortDirection == PortDirection.Out)
        {
          Console.ForegroundColor = ConsoleColor.Red;
          Console.WriteLine("Error: cannot add output port '{0}' to sensitivity list", signal.Name);
          Console.ResetColor();
        }
        else
          combinatorialProcess.AddSignalToSensivitityList(signal);
      }

      combinatorialProcess.AddSignalToSensivitityList(stateSignal);
      combinatorialProcess.AddSignalToSensivitityList(commandSignal);

      foreach (VhdlRegister register in Registers.Values)
      {
        SignalAssignmentStatement registerInitStatement =
          new SignalAssignmentStatement(
            register.WriteSignal,
            new SignalReferenceExpression(
							combinatorialProcess.Statements,
							register.ReadSignal
						)
          );

        combinatorialProcess.Statements.InsertFirst(registerInitStatement);
      }
    }

    private void createCommandType()
    {
      commandType = new LitteralBasedEnumerationType(2, "command_type");

      foreach (Gezel.Model.Transition t in controller.Transitions.Values)
      {
        getFiniteStateMachineStates(t);
      }

      foreach (FsmState fs in fsmStates)
      {
        if (!commandType.ContainsMember(fs.Name))
          commandType.AddTypeMember(fs.Name);
      }

      AddDeclarativeItem(commandType);

      commandSignal = new Signal(commandType, null, "command"); //  VhdlRegister(this, commandType, 1, "command");

      AddSignal(commandSignal);
      AddDeclarativeItem(commandSignal);

    }

    private void createStateType()
    {
      stateType = new LitteralBasedEnumerationType(2, "state_type");
      foreach (Gezel.Model.State state in Controller.States.Values)
        stateType.AddTypeMember("state_" + state.LocalName);

      AddDeclarativeItem(stateType);

      stateSignal = new Signal(stateType, null, "state");

      AddSignal(stateSignal);
      AddDeclarativeItem(stateSignal);
    }

    void getFiniteStateMachineStates(Gezel.Model.Transition t)
    {
      if (t.FirstTransition != null)
      {
        getFiniteStateMachineStates(t.FirstTransition);
      }

      if (t.FirstTargetSfgs != null && t.FirstTargetSfgs.Length > 0)
      {
        FsmState s = new FsmState(t.FirstTargetSfgs);

        bool found = false;
        foreach (FsmState fs in fsmStates)
          if (fs.Name == s.Name)
            found = true;

        if (!found)
          fsmStates.Add(new FsmState(t.FirstTargetSfgs));
      }

      if (t.SecondTransition != null)
      {
        getFiniteStateMachineStates(t.SecondTransition);
      }
      if (t.SecondTargetSfgs != null && t.SecondTargetSfgs.Length > 0)
      {
        FsmState s = new FsmState(t.SecondTargetSfgs);

        bool found = false;
        foreach (FsmState fs in fsmStates)
          if (fs.Name == s.Name)
            found = true;

        if (!found)
          fsmStates.Add(new FsmState(t.SecondTargetSfgs));
      }
    }

    private void createStateMachine(
      Gezel.Model.Transition t,
      StatementCollection regStatements,
      StatementCollection cmbStatements
    )
    {
      switch (t.TransitionType)
      {
        case Weazel.Gezel.Model.TransitionType.SimpleUnconditional:
          {
            createAndAddStateAssignment(
              regStatements,
              t.FirstTargetState
            );

            createAndAddCommandAssignment(
              cmbStatements,
              t.FirstTargetSfgs
            );
            return;
          }
        case Weazel.Gezel.Model.TransitionType.SimpleConditional:
          {
            IfStatement regIfStatement = new IfStatement();
            IfStatement cmbIfStatement = new IfStatement();

            Vhdl.Expressions.Expression expr =
              cmbProcessesExprConverter.ConvertExpression(
                cmbProcess.Statements, 
                t.Condition,
                true                
              );
                    
            cmbIfStatement.Condition = expr;
            cmbIfStatement.Resolve(this, cmbProcess, cmbStatements);
            regIfStatement.Condition = expr;
            regIfStatement.Resolve(this, regProcess, regStatements);

            createAndAddStateAssignment(regIfStatement.TrueStatements, t.FirstTargetState);
            createAndAddStateAssignment(regIfStatement.FalseStatements, t.SecondTargetState);

            createAndAddCommandAssignment(cmbIfStatement.TrueStatements, t.FirstTargetSfgs);
            createAndAddCommandAssignment(cmbIfStatement.FalseStatements, t.SecondTargetSfgs);

            regStatements.Add(regIfStatement);
            cmbStatements.Add(cmbIfStatement);

            break;
          }
        case Weazel.Gezel.Model.TransitionType.Nested:
          {
            IfStatement regIfStatement = new IfStatement();
            IfStatement cmbIfStatement = new IfStatement();

            // convert condition expression
            Vhdl.Expressions.Expression expr =
              cmbProcessesExprConverter.ConvertExpression(
                cmbProcess.Statements,
                t.Condition,
                true
              );

            regIfStatement.Condition = expr;
						regIfStatement.Resolve(this, regProcess, regStatements);

            cmbIfStatement.Condition = expr;
            cmbIfStatement.Resolve(this, cmbProcess, cmbStatements);
                     
            regStatements.Add(regIfStatement);
            cmbStatements.Add(cmbIfStatement);

            createStateMachine(
              t.FirstTransition,
              regIfStatement.TrueStatements,
              cmbIfStatement.TrueStatements
            );

            createStateMachine(
              t.SecondTransition,
              regIfStatement.FalseStatements,
              cmbIfStatement.FalseStatements  
            );

            return;
          }

        default:
          throw new NotSupportedException();
      }
    }

    private void createAndAddStateAssignment(StatementCollection statements, string targetState)
    {
      SignalAssignmentStatement sas =
        new SignalAssignmentStatement(
          stateSignal,
          new Vhdl.Expressions.EnumerationMemberReferenceExpression(
						statements,
            stateType,
            "state_" + targetState
          )
        );

      statements.Add(sas);
    }

    private void createAndAddCommandAssignment(StatementCollection statements, string[] activeSfgs)
    {
      FsmState s = new FsmState(activeSfgs);

      SignalAssignmentStatement sas =
        new SignalAssignmentStatement(
          commandSignal,
          new Vhdl.Expressions.EnumerationMemberReferenceExpression(
						statements,
            commandType,
            s.Name
          )
        );

      statements.Add(sas);
    }
  }
}
